package cn.inurl.component.treetable;
import java.io.IOException;
import java.io.File;
import java.util.Date;
import java.util.Stack;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;

public class FileSystemModel2 extends AbstractTreeTableModel {

	// Names of the columns.
	static protected String[] cNames = { "Name", "Size", "Type", "Modified" };

	// Types of the columns.
	static protected Class[] cTypes = { TreeTableModel.class, Integer.class,
			String.class, Date.class };

	// The the returned file length for directories.
	public static final Integer ZERO = new Integer(0);

	/** An array of MergeSorter sorters, that will sort based on size. */
	static Stack sorters = new Stack();

	/**
	 * True if the receiver is valid, once set to false all Threads loading
	 * files will stop.
	 */
	protected boolean isValid;

	/**
	 * Node currently being reloaded, this becomes somewhat muddy if reloading
	 * is happening in multiple threads.
	 */
	protected FileNode reloadNode;

	/** > 0 indicates reloading some nodes. */
	int reloadCount;

	/** Returns true if links are to be descended. */
	protected boolean descendLinks;

	/**
	 * Returns a MergeSort that can sort on the totalSize of a FileNode.
	 */
	protected static MergeSort getSizeSorter() {
		synchronized (sorters) {
			if (sorters.size() == 0) {
				return new SizeSorter();
			}
			return (MergeSort) sorters.pop();
		}
	}

	/**
	 * Should be invoked when a MergeSort is no longer needed.
	 */
	protected static void recycleSorter(MergeSort sorter) {
		synchronized (sorters) {
			sorters.push(sorter);
		}
	}

	public FileSystemModel2() {
		this(File.separator);
	}

	public FileSystemModel2(String rootPath) {
		super(null);
		isValid = true;
		root = new FileNode(new File(rootPath));
	}

	public int getChildCount(Object node) {
		Object[] children = getChildren(node);
		return (children == null) ? 0 : children.length;
	}

	public Object getChild(Object node, int i) {
		return getChildren(node)[i];
	}

	public boolean isLeaf(Object node) {
		return ((FileNode) node).isLeaf();
	}

	public int getColumnCount() {
		return cNames.length;
	}

	public String getColumnName(int column) {
		return cNames[column];
	}

	public Class getColumnClass(int column) {
		return cTypes[column];
	}

	public Object getValueAt(Object node, int column) {
		FileNode fn = (FileNode) node;

		try {
			switch (column) {
			case 0:
				return fn.getFile().getName();
			case 1:
				if (fn.isTotalSizeValid()) {
					return new Integer((int) ((FileNode) node).totalSize());
				}
				return null;
			case 2:
				return fn.isLeaf() ? "File" : "Directory";
			case 3:
				return fn.lastModified();
			}
		} catch (SecurityException se) {
		}

		return null;
	}

	public void reloadChildren(Object node) {
		FileNode fn = (FileNode) node;

		synchronized (this) {
			reloadCount++;
		}
		fn.resetSize();
		new Thread(new FileNodeLoader((FileNode) node)).start();
	}

	public void stopLoading() {
		isValid = false;
		synchronized (this) {
			while (reloadCount > 0) {
				try {
					wait();
				} catch (InterruptedException ie) {
				}
			}
		}
		isValid = true;
	}

	public void setDescendsLinks(boolean newValue) {
		descendLinks = newValue;
	}

	public boolean getDescendsLinks() {
		return descendLinks;
	}

	public String getPath(Object node) {
		return ((FileNode) node).getFile().getPath();
	}

	public long getTotalSize(Object node) {
		return ((FileNode) node).totalSize();
	}

	public boolean isReloading() {
		return (reloadCount > 0);
	}

	public TreePath getPathLoading() {
		FileNode rn = reloadNode;

		if (rn != null) {
			return new TreePath(rn.getPath());
		}
		return null;
	}

	public Object getNodeLoading() {
		return reloadNode;
	}

	protected File getFile(Object node) {
		FileNode fileNode = ((FileNode) node);
		return fileNode.getFile();
	}

	protected Object[] getChildren(Object node) {
		FileNode fileNode = ((FileNode) node);
		return fileNode.getChildren();
	}

	protected static FileNode[] EMPTY_CHILDREN = new FileNode[0];

	static private MergeSort fileMS = new MergeSort() {
		public int compareElementsAt(int beginLoc, int endLoc) {
			return ((String) toSort[beginLoc])
					.compareTo((String) toSort[endLoc]);
		}
	};

	class FileNode {
		/** java.io.File the receiver represents. */
		protected File file;

		/** Parent FileNode of the receiver. */
		private FileNode parent;

		/** Children of the receiver. */
		protected FileNode[] children;

		/** Size of the receiver and all its children. */
		protected long totalSize;

		/** Valid if the totalSize has finished being calced. */
		protected boolean totalSizeValid;

		/** Path of the receiver. */
		protected String canonicalPath;

		/**
		 * True if the canonicalPath of this instance does not start with the
		 * canonical path of the parent.
		 */
		protected boolean isLink;

		/** Date last modified. */
		protected Date lastModified;

		protected FileNode(File file) {
			this(null, file);
		}

		protected FileNode(FileNode parent, File file) {
			this.parent = parent;
			this.file = file;
			try {
				canonicalPath = file.getCanonicalPath();
			} catch (IOException ioe) {
				canonicalPath = "";
			}
			if (parent != null) {
				isLink = !canonicalPath.startsWith(parent.getCanonicalPath());
			} else {
				isLink = false;
			}
			if (isLeaf()) {
				totalSize = file.length();
				totalSizeValid = true;
			}
		}

		/**
		 * Returns the date the receiver was last modified.
		 */
		public Date lastModified() {
			if (lastModified == null && file != null) {
				lastModified = new Date(file.lastModified());
			}
			return lastModified;
		}

		/**
		 * Returns the the string to be used to display this leaf in the JTree.
		 */
		public String toString() {
			return file.getName();
		}

		/**
		 * Returns the java.io.File the receiver represents.
		 */
		public File getFile() {
			return file;
		}

		/**
		 * Returns size of the receiver and all its children.
		 */
		public long totalSize() {
			return totalSize;
		}

		/**
		 * Returns the parent of the receiver.
		 */
		public FileNode getParent() {
			return parent;
		}

		/**
		 * Returns true if the receiver represents a leaf, that is it is isn't a
		 * directory.
		 */
		public boolean isLeaf() {
			return file.isFile();
		}

		/**
		 * Returns true if the total size is valid.
		 */
		public boolean isTotalSizeValid() {
			return totalSizeValid;
		}

		/**
		 * Clears the date.
		 */
		protected void resetLastModified() {
			lastModified = null;
		}

		/**
		 * Sets the size of the receiver to be 0.
		 */
		protected void resetSize() {
			alterTotalSize(-totalSize);
		}

		/**
		 * Loads the children, caching the results in the children instance
		 * variable.
		 */
		protected FileNode[] getChildren() {
			return children;
		}

		/**
		 * Recursively loads all the children of the receiver.
		 */
		protected void loadChildren(MergeSort sorter) {
			totalSize = file.length();
			children = createChildren(null);
			for (int counter = children.length - 1; counter >= 0; counter--) {
				Thread.yield(); // Give the GUI CPU time to draw itself.
				if (!children[counter].isLeaf()
						&& (descendLinks || !children[counter].isLink())) {
					children[counter].loadChildren(sorter);
				}
				totalSize += children[counter].totalSize();
				if (!isValid) {
					counter = 0;
				}
			}
			if (isValid) {
				if (sorter != null) {
					sorter.sort(children);
				}
				totalSizeValid = true;
			}
		}

		/**
		 * Loads the children of of the receiver.
		 */
		protected FileNode[] createChildren(MergeSort sorter) {
			FileNode[] retArray = null;

			try {
				String[] files = file.list();
				if (files != null) {
					if (sorter != null) {
						sorter.sort(files);
					}
					retArray = new FileNode[files.length];
					String path = file.getPath();
					for (int i = 0; i < files.length; i++) {
						File childFile = new File(path, files[i]);
						retArray[i] = new FileNode(this, childFile);
					}
				}
			} catch (SecurityException se) {
			}
			if (retArray == null) {
				retArray = EMPTY_CHILDREN;
			}
			return retArray;
		}

		/**
		 * Returns true if the children have been loaded.
		 */
		protected boolean loadedChildren() {
			return (file.isFile() || (children != null));
		}

		/**
		 * Gets the path from the root to the receiver.
		 */
		public FileNode[] getPath() {
			return getPathToRoot(this, 0);
		}

		/**
		 * Returns the canonical path for the receiver.
		 */
		public String getCanonicalPath() {
			return canonicalPath;
		}

		/**
		 * Returns true if the receiver's path does not begin with the parent's
		 * canonical path.
		 */
		public boolean isLink() {
			return isLink;
		}

		protected FileNode[] getPathToRoot(FileNode aNode, int depth) {
			FileNode[] retNodes;

			if (aNode == null) {
				if (depth == 0)
					return null;
				else
					retNodes = new FileNode[depth];
			} else {
				depth++;
				retNodes = getPathToRoot(aNode.getParent(), depth);
				retNodes[retNodes.length - depth] = aNode;
			}
			return retNodes;
		}

		/**
		 * Sets the children of the receiver, updates the total size, and if
		 * generateEvent is true a tree structure changed event is created.
		 */
		protected void setChildren(FileNode[] newChildren, boolean generateEvent) {
			long oldSize = totalSize;

			totalSize = file.length();
			children = newChildren;
			for (int counter = children.length - 1; counter >= 0; counter--) {
				totalSize += children[counter].totalSize();
			}

			if (generateEvent) {
				FileNode[] path = getPath();

				fireTreeStructureChanged(FileSystemModel2.this, path, null,
						null);

				FileNode parent = getParent();

				if (parent != null) {
					parent.alterTotalSize(totalSize - oldSize);
				}
			}
		}

		protected synchronized void alterTotalSize(long sizeDelta) {
			if (sizeDelta != 0 && (parent = getParent()) != null) {
				totalSize += sizeDelta;
				nodeChanged();
				parent.alterTotalSize(sizeDelta);
			} else {
				// Need a way to specify the root.
				totalSize += sizeDelta;
			}
		}

		/**
		 * This should only be invoked on the event dispatching thread.
		 */
		protected synchronized void setTotalSizeValid(boolean newValue) {
			if (totalSizeValid != newValue) {
				nodeChanged();
				totalSizeValid = newValue;

				FileNode parent = getParent();

				if (parent != null) {
					parent.childTotalSizeChanged(this);
				}
			}
		}

		/**
		 * Marks the receivers total size as valid, but does not invoke node
		 * changed, nor message the parent.
		 */
		protected synchronized void forceTotalSizeValid() {
			totalSizeValid = true;
		}

		/**
		 * Invoked when a childs total size has changed.
		 */
		protected synchronized void childTotalSizeChanged(FileNode child) {
			if (totalSizeValid != child.isTotalSizeValid()) {
				if (totalSizeValid) {
					setTotalSizeValid(false);
				} else {
					FileNode[] children = getChildren();

					for (int counter = children.length - 1; counter >= 0; counter--) {
						if (!children[counter].isTotalSizeValid()) {
							return;
						}
					}
					setTotalSizeValid(true);
				}
			}

		}

		/**
		 * Can be invoked when a node has changed, will create the appropriate
		 * event.
		 */
		protected void nodeChanged() {
			FileNode parent = getParent();

			if (parent != null) {
				FileNode[] path = parent.getPath();
				int[] index = { getIndexOfChild(parent, this) };
				Object[] children = { this };

				fireTreeNodesChanged(FileSystemModel2.this, path, index,
						children);
			}
		}
	}

	class FileNodeLoader implements Runnable {
		/** Node creating children for. */
		FileNode node;

		/** Sorter. */
		MergeSort sizeMS;

		FileNodeLoader(FileNode node) {
			this.node = node;
			node.resetLastModified();
			node.setChildren(node.createChildren(fileMS), true);
			node.setTotalSizeValid(false);
		}

		public void run() {
			FileNode[] children = node.getChildren();

			sizeMS = getSizeSorter();
			for (int counter = children.length - 1; counter >= 0; counter--) {
				if (!children[counter].isLeaf()) {
					reloadNode = children[counter];
					loadChildren(children[counter]);
					reloadNode = null;
				}
				if (!isValid) {
					counter = 0;
				}
			}
			recycleSorter(sizeMS);
			if (isValid) {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						MergeSort sorter = getSizeSorter();

						sorter.sort(node.getChildren());
						recycleSorter(sorter);
						node.setChildren(node.getChildren(), true);
						synchronized (FileSystemModel2.this) {
							reloadCount--;
							FileSystemModel2.this.notifyAll();
						}
					}
				});
			} else {
				synchronized (FileSystemModel2.this) {
					reloadCount--;
					FileSystemModel2.this.notifyAll();
				}
			}
		}

		protected void loadChildren(FileNode node) {
			if (!node.isLeaf() && (descendLinks || !node.isLink())) {
				final FileNode[] children = node.createChildren(null);

				for (int counter = children.length - 1; counter >= 0; counter--) {
					if (!children[counter].isLeaf()) {
						if (descendLinks || !children[counter].isLink()) {
							children[counter].loadChildren(sizeMS);
						} else {
							children[counter].forceTotalSizeValid();
						}
					}
					if (!isValid) {
						counter = 0;
					}
				}
				if (isValid) {
					final FileNode fn = node;

					// Reset the children
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							MergeSort sorter = getSizeSorter();

							sorter.sort(children);
							recycleSorter(sorter);
							fn.setChildren(children, true);
							fn.setTotalSizeValid(true);
							fn.nodeChanged();
						}
					});
				}
			} else {
				node.forceTotalSizeValid();
			}
		}
	}

	static class SizeSorter extends MergeSort {
		public int compareElementsAt(int beginLoc, int endLoc) {
			long firstSize = ((FileNode) toSort[beginLoc]).totalSize();
			long secondSize = ((FileNode) toSort[endLoc]).totalSize();

			if (firstSize != secondSize) {
				return (int) (secondSize - firstSize);
			}
			return ((FileNode) toSort[beginLoc]).toString().compareTo(
					((FileNode) toSort[endLoc]).toString());
		}
	}
}
